package org.song.filter.authentication;

import cn.hutool.core.lang.Assert;
import lombok.extern.slf4j.Slf4j;
import org.song.cache.UserPermissionCache;
import org.song.exception.MyAccessDeniedException;
import org.song.model.entity.ApiEntity;
import org.song.service.ApiService;
import org.song.util.JacksonUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import javax.servlet.http.HttpServletRequest;
import java.util.Optional;
import java.util.Set;

/**
 * @author song
 * @date 2022/3/10 20:19
 * 自定义动态权限过滤器
 **/

@Slf4j
@Component(value = "myDynamicPermission")
public class MyDynamicPermission {


    @Autowired
    @Qualifier("backendApiServiceImpl")
    private ApiService apiService;

    @Autowired
    @Qualifier( value = "userPermissionCacheImpl")
    private UserPermissionCache userPermissionCache;

    /**
     * 自定义权限过滤方法，用户登录成功之后会被递归调用链调用走自定义方法校验权限
     *
     * @param request
     * @param authentication
     * @return
     * @throws MyAccessDeniedException
     */
    public boolean checkAuthority(HttpServletRequest request, Authentication authentication) throws Throwable {

        // 从ThreadLocal中取出到该用户的类型 判断 该验证信息是否是 UserDetails 类型
        final Object principal = authentication.getPrincipal();
        Assert.isTrue((principal instanceof UserDetails), () -> {
            log.error("当前登录用户不是springSecurity自带用户类型，拒绝访问\n" + JacksonUtil.getJsonString(principal));
            throw new MyAccessDeniedException("不是UserDetails类型！");
        });
        // 将获取的认证对象 强转为 UserDetails 类型
        final UserDetails userDetails = (UserDetails) principal;
        // 拿到当前用户的用户名
        final String username = userDetails.getUsername();
        // 通过用户名 从缓存获取 该用户对应的后端 API 接口 权限 缓存失效从库里抓取写回缓存 TODO: 缓存穿透
        Set<ApiEntity> apiList = Optional.ofNullable( userPermissionCache.getUserPermissionList( username ) )
                .orElseGet( () -> {
                    log.info( "当前用户缓存权限信息失效，从DB抓取写回缓存" );
                    final Set<ApiEntity> apiSetByUserName = apiService.getApiSetByUserName(username);
                    // 写回缓存
                    userPermissionCache.setUserPermissionList( username , apiSetByUserName );
                    return apiSetByUserName;
                } );

        final AntPathMatcher antPathMatcher = new AntPathMatcher();
        //当前访问路径
        final String requestUri = request.getRequestURI();
        final String urlMethod = request.getMethod();
        final boolean result = apiList.stream().anyMatch( (item) -> {
            // 使用antPathMatcher 对象判断本次请求的 url 是否在API集合中
            boolean hashAntPath = antPathMatcher.match( item.getUrl() , requestUri );
            //判断请求方式是否和数据库中匹配（数据库存储：GET,POST,PUT,DELETE）
            final String dbMethod = Optional.ofNullable( item.getMethod() ).orElse("");
            // 返回本次请求的方式 是否在API 对应的请求方式之内。如果不在则返回 -1
            // 因为有的url 可能包含多个请求方式
            int hasMethod = dbMethod.indexOf(urlMethod);
            //两者都成立，权限和请求方式都通过
            return hashAntPath && ( hasMethod != -1 );
        });

        //返回
        if (result) {
            return true;
        } else {
            throw new MyAccessDeniedException("您没有访问权限！");
        }

    }


}
